<template>
  <div class="three-container" ref="container">
    <div v-if="isLoading" class="loading-overlay">
      <div class="preview" v-if="previewImageUrl">
        <img :src="previewImageUrl" alt="预览图" />
      </div>
      <div class="loading-text">正在加载模型 {{ Math.round(loadingProgress) }}%</div>
      <div class="progress">
        <div class="bar" :style="{ width: loadingProgress + '%' }"></div>
      </div>
    </div>
  </div>
  
</template>

<script>
import * as THREE from 'three';
import { markRaw } from 'vue';
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader';
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import JSZip from 'jszip';

export default {
  props: {
    modelZipUrl: {
      type: String,
      default: ''
    },
    previewImageUrl: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      scene: null,
      camera: null,
      renderer: null,
      model: null,
      isLoading: false,
      loadingProgress: 0,
      urlMap: new Map(),
    };
  },
  mounted() {
    this.initThree();
    this.animate();
    if (this.modelZipUrl) {
      this.loadZipAndRender(this.modelZipUrl);
    }
  },
  watch: {
    modelZipUrl(newUrl) {
      if (newUrl) {
        this.clearScene();
        this.loadZipAndRender(newUrl);
      }
    }
  },
  methods: {
    initThree() {
      // 1. 创建场景
      this.scene = markRaw(new THREE.Scene());
      this.scene.background = new THREE.Color(0x222222); // 深色背景

      // 2. 创建相机（透视相机）
      this.camera = markRaw(new THREE.PerspectiveCamera(
        75,
        this.$refs.container.clientWidth / this.$refs.container.clientHeight,
        0.1,
        1000
      ));
      this.camera.position.z = 5; // 相机初始位置

      // 3. 创建渲染器
      this.renderer = markRaw(new THREE.WebGLRenderer({ antialias: true, alpha: false }));
      this.renderer.outputColorSpace = THREE.SRGBColorSpace;
      this.renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
      this.renderer.setSize(
        this.$refs.container.clientWidth,
        this.$refs.container.clientHeight
      );
      this.$refs.container.appendChild(this.renderer.domElement);

      // 基础光照，避免模型全黑
      const ambient = new THREE.AmbientLight(0xffffff, 0.8);
      this.scene.add(ambient);
      const dir = new THREE.DirectionalLight(0xffffff, 0.6);
      dir.position.set(3, 5, 2);
      this.scene.add(dir);

      // 交互控制
      this.controls = markRaw(new OrbitControls(this.camera, this.renderer.domElement));
      this.controls.enableDamping = true;
      this.controls.target.set(0, 0, 0);

      // 帮助可视化
      const axes = new THREE.AxesHelper(1.5);
      this.scene.add(axes);
      const grid = new THREE.GridHelper(10, 10, 0x444444, 0x333333);
      grid.position.y = -1.2;
      this.scene.add(grid);

      // 监听尺寸变化
      window.addEventListener('resize', this.handleResize);
    },
    handleResize() {
      if (!this.renderer || !this.camera) return;
      const w = this.$refs.container.clientWidth;
      const h = this.$refs.container.clientHeight;
      this.camera.aspect = w / Math.max(h, 1);
      this.camera.updateProjectionMatrix();
      this.renderer.setSize(w, h);
    },
    toProxyUrl(url) {
      try {
        const u = new URL(url);
        // 命中 COS 域名则走 Vite 代理 /cos
        if (u.hostname.includes('tencentcos.cn')) {
          return '/cos' + u.pathname + (u.search || '');
        }
        return url;
      } catch {
        return url;
      }
    },
    async loadZipAndRender(zipUrl) {
      try {
        this.isLoading = true;
        this.loadingProgress = 0;
        this.urlMap.clear();

        const response = await fetch(this.toProxyUrl(zipUrl));
        const arrayBuffer = await response.arrayBuffer();
        const zip = await JSZip.loadAsync(arrayBuffer);

        // 生成所有文件的 Blob URL 映射
        const entries = Object.values(zip.files);
        let objEntry = null;
        let mtlEntry = null;

        for (const entry of entries) {
          if (entry.dir) continue;
          const lower = entry.name.toLowerCase();
          if (lower.endsWith('.obj')) objEntry = entry;
          if (lower.endsWith('.mtl')) mtlEntry = entry;
        }

        // 为所有资源创建 URL（纹理等）
        const resourcePromises = entries.filter(f => !f.dir).map(async (file) => {
          const blob = await file.async('blob');
          const url = URL.createObjectURL(blob);
          const normalized = file.name.replace(/^\.\//, '');
          this.urlMap.set(normalized, url);
        });
        await Promise.all(resourcePromises);

        if (!objEntry) {
          throw new Error('压缩包中未找到 OBJ 文件');
        }

        const manager = new THREE.LoadingManager();
        manager.onProgress = (_url, itemsLoaded, itemsTotal) => {
          this.loadingProgress = (itemsLoaded / Math.max(itemsTotal, 1)) * 100;
          this.$emit('progress', this.loadingProgress);
        };
        // 将资源 URL 映射为 blob URL
        manager.setURLModifier((url) => {
          const cleaned = url.replace(/^\.\//, '');
          return this.urlMap.get(cleaned) || url;
        });

        const objBlob = await objEntry.async('blob');
        const objUrl = URL.createObjectURL(objBlob);

        const objLoader = new OBJLoader(manager);

        if (mtlEntry) {
          const mtlText = await mtlEntry.async('text');
          const mtlLoader = new MTLLoader(manager);
          const materials = mtlLoader.parse(mtlText);
          materials.preload();
          objLoader.setMaterials(materials);
        }

        objLoader.load(
          objUrl,
          (object) => {
            this.model = markRaw(object);
            // 尺寸与居中处理
            const box = new THREE.Box3().setFromObject(this.model);
            const size = new THREE.Vector3();
            box.getSize(size);
            const maxDim = Math.max(size.x, size.y, size.z) || 1;
            const targetSize = 2.5; // 目标最大尺寸
            const scale = targetSize / maxDim;
            this.model.scale.setScalar(scale);
            const center = new THREE.Vector3();
            box.getCenter(center);
            this.model.position.sub(center.multiplyScalar(scale));

            this.scene.add(this.model);

            // 相机对焦框选对象
            const sphere = new THREE.Sphere();
            new THREE.Box3().setFromObject(this.model).getBoundingSphere(sphere);
            const fov = this.camera.fov * (Math.PI / 180);
            const dist = (sphere.radius / Math.sin(fov / 2)) * 1.2;
            this.camera.position.set(sphere.center.x + dist, sphere.center.y + dist * 0.4, sphere.center.z + dist);
            this.camera.near = Math.max(0.01, dist / 100);
            this.camera.far = dist * 100;
            this.camera.updateProjectionMatrix();
            if (this.controls) {
              this.controls.target.copy(sphere.center);
              this.controls.update();
            }

            this.isLoading = false;
            this.loadingProgress = 100;
            this.$emit('loaded');
          },
          (xhr) => {
            if (xhr.lengthComputable) {
              this.loadingProgress = (xhr.loaded / xhr.total) * 100;
              this.$emit('progress', this.loadingProgress);
            }
          },
          (err) => {
            console.error('OBJ 加载失败', err);
            this.isLoading = false;
            this.$emit('error', err);
          }
        );
      } catch (e) {
        console.error('模型解压或加载失败', e);
        this.isLoading = false;
        this.$emit('error', e);
      }
    },
    animate() {
      requestAnimationFrame(this.animate.bind(this));
      // 让模型旋转（简单动画）
      if (this.controls) this.controls.update();
      this.renderer.render(this.scene, this.camera);
    },
    clearScene() {
      if (this.model) {
        this.scene.remove(this.model);
        this.model.traverse((child) => {
          if (child.isMesh) {
            child.geometry?.dispose?.();
            if (child.material) {
              const materials = Array.isArray(child.material) ? child.material : [child.material];
              materials.forEach((m) => {
                m.map?.dispose?.();
                m.normalMap?.dispose?.();
                m.roughnessMap?.dispose?.();
                m.metalnessMap?.dispose?.();
                m.dispose?.();
              });
            }
          }
        });
        this.model = null;
      }
      // 回收 URL
      this.urlMap.forEach((u) => URL.revokeObjectURL(u));
      this.urlMap.clear();
    }
  },
  beforeUnmount() {
    // 销毁资源，防止内存泄漏
    if (this.renderer) {
      this.renderer.dispose();
    }
    this.clearScene();
    window.removeEventListener('resize', this.handleResize);
  },
};
</script>

<style scoped>
.three-container {
  width: 100%;
  height: 600px; /* 可根据需求调整高度 */
  position: relative;
}
.loading-overlay {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: rgba(0,0,0,0.4);
  color: #fff;
  gap: 12px;
}
.loading-overlay .preview img {
  width: 160px;
  height: 160px;
  object-fit: contain;
  border-radius: 8px;
  background: #fff;
}
.progress { width: 60%; height: 8px; background: rgba(255,255,255,0.2); border-radius: 999px; overflow: hidden; }
.progress .bar { height: 100%; background: #42b983; transition: width 0.2s ease; }
</style>